package SSF.OS.OSPFv2.test;

/**
 * Configurator.java
 *
 * @author Dirk Jacob
 * @version 0.2.1
 */

import java.util.*;
import SSF.OS.*;
import com.renesys.raceway.DML.*;

/** 
 * Implements a pseudo-protocol, which updates an OSPF session at times, 
 * configured in the DML file. The utility can be configured as follows:
 * <pre>
 *   ProtocolSession[ name configurator use SSF.OS.OSPF.test.Configurator 
 *       update [
 *           time <time>
 *
 *           ... OSPF-DML config ...
 *   ]
 * </pre>
 */ 
public class Configurator extends ProtocolSession {
	
	/** reference to the associated ospf-session */
	private ProtocolSession ospfSession;
	/** the configuration events */
	private Vector configEvents;
	/** the queue with pending events */
	private Enumeration EventQueue;
	/** the event, which is coming up next */
	private UpdateEvent pendingEvent;
	/** the event timer */
	private SSF.OS.Timer timer;
	
	/** the configuration keys for an OSPF area configuration */
	private String[] area_values = { "id", "stub", "stub_default_cost" };
	/** the configuration keys for an OSPF interface configuration */
	private String[] if_values = { "id", "cost", "priority", "transmit-delay",
	                                "hello-interval", "dead-interval", "retransmit-interval" };
	
	/** Constructor */
	public Configurator() {
		
		configEvents = new Vector();
		ospfSession = null;
		
	}
	
	/** Configures this ProtocolSession according to the DML config */
	public void config(Configuration cfg) throws configException {
		super.config(cfg);
		
		// walk through all updates of the configuration
		for(Enumeration events = cfg.find("update"); events.hasMoreElements();) {
			
			//Configuration ecfg = (Configuration)events.nextElement();
			dmlConfig ecfg = (dmlConfig)events.nextElement();
			
			// first get the time...
			double t = Double.parseDouble((String)ecfg.findSingle("time"));
					
			// create a new UpdateEvent with appropriate data
			UpdateEvent evt = new UpdateEvent();
			
			evt.cfg = new dmlConfig();
						
			try {
				// add the is_update flag to the configuration
				evt.cfg.add("is_update","true");			
			
//				// TODO! Problems to be solved here! DML implementation is buggy! (in ssfnet 1.2)
//              //       perhaps, this has been fixed in 1.4?
//				// copy config data
//				// simply adding the configuration seems not to work, so we
//				// have to walk through the tree
//				for(Enumeration areas = ecfg.find("area"); areas.hasMoreElements();) {
//					dmlConfig cur = (dmlConfig)areas.nextElement();
//					//dmlConfig tmp = evt.cfg.add("");
//					// don't know, if this works! -- obviously not!
//					//evt.cfg.add( cur );	
//					evt.cfg.add( cur );
//					
//					dmlView.createDmlWindows(new java.awt.Frame(), evt.cfg);
//				}
                                for(Enumeration areas = ecfg.find("area"); areas.hasMoreElements();) {
                                        // copy the actual area config
                                        dmlConfig current = (dmlConfig)areas.nextElement();
                                        
                                        dmlConfig tmp;
                                        String val;
                                        tmp = evt.cfg.add("area");
                                        
                                        for( int i = 0; i < area_values.length; i++ ) {
                                                val = (String)current.findSingle(area_values[i]);
                                                if (val != null) {
                                                        tmp.add(area_values[i], val);
                                                }
                                        }
                                        
                                        // each area can have multiple if configs
                                        for(Enumeration ifcs = current.find("if"); ifcs.hasMoreElements();) {
                                                current = (dmlConfig)ifcs.nextElement();
                                                dmlConfig tmp2 = tmp.add("if");
                                                
                                                for( int i = 0; i < if_values.length; i++ ) {
                                                        val = (String)current.findSingle(if_values[i]);
                                                        if (val != null) {
                                                                tmp2.add(if_values[i], val);
                                                        }
                                                }       
                                        }
                                }
                                //dmlView.createDmlWindows(new java.awt.Frame(), evt.cfg);                               
				
			} catch (dmlException dex ) {
				throw new configException( "operation on dmlConfig failed." );
			}
				
			evt.t = t;	
			
			// now, the newly created event must be placed at the right place
			// in the event queue
			double evt_t = -1.0;
 			int index = 0;
 			boolean finished = false;
 			
 			Enumeration ce = configEvents.elements();
 			
 			while (!finished) {
 				if(ce.hasMoreElements()) {
 					evt_t = ((UpdateEvent)ce.nextElement()).t;
 					
 					if(evt_t < t) {
 						index++;
 					} else {
 						finished = true;
 					}
 				} else {
 					finished = true;
 				}
 			}
 			
 			configEvents.add(index, evt);
		}
	}
	
	/** Initializes the Configurator and starts all timers */
	public void init() throws ProtocolException {
		
		ospfSession = inGraph().SessionForName("ospf");
		
		// initialize the event queue
		EventQueue = configEvents.elements();
  		
  		// look, which event is coming next
  		pendingEvent = getNextEvent();
  		
  		// if there is such an event, set the timer accordingly - if there's no
  		// event, do nothing further...
  		if (pendingEvent != null) {
  			
  			// calculate, how long the timer has to wait
  			long exp_time = SSF.Net.Net.seconds(pendingEvent.t) - inGraph().now();
  			
  			// create a new timer object, which calls timerEvent() as a callback
 			timer = new SSF.OS.Timer(inGraph(), exp_time){
 				public void callback() {
 					timerEvent();
 				}
 			};
 		 		
 		 	// finally start the timer
 			timer.set( exp_time );
 		}
		
	}
	
	/** Receives the next event from the event queue */
	private UpdateEvent getNextEvent() {
  		
  		UpdateEvent evt = null;
  		
  		if (EventQueue.hasMoreElements()) {
  			  evt = (UpdateEvent)EventQueue.nextElement();			
  		}
  		
  		return evt;
  	}
  	
  	 /** The callback method of the timer */ 	
  	private void timerEvent() {
  		
  		// when an event fires, push an update message into the OSPF session
  		try {
  			if (pendingEvent != null) {
  				sendMessage((Configuration)pendingEvent.cfg);
  			}
  		} catch (ProtocolException e) {
  			System.err.println(e);
  		}
  		
  		// when done, restart the timer with a new event pending
  		pendingEvent = getNextEvent();
  		
  		if( pendingEvent != null ) {
  			long exp_time = SSF.Net.Net.seconds(pendingEvent.t) - inGraph().now();
  			
  			timer.set( exp_time );
  		}
  	}
  	
  	/** Sends a configuration update to OSPF */
	private void sendMessage( Configuration cfgUpdate ) throws ProtocolException {
		// TODO: Can we simply send the Configuration to the OSPF session or
		// do we have to copy it and send the copy instead???
				
		// pack the configuration into a ConfigUpdateMessage
		ConfigUpdateMessage updateMsg = new ConfigUpdateMessage( cfgUpdate );
		
		// finally, push the newly created Message into the OSPF session
		if (ospfSession != null) {	
			ospfSession.push( updateMsg, this );
		}
	}
	
	/** push method - must be implemented, since this is a ProtocolSession. This method must
	  * never be called. 
	  */
	public boolean push(ProtocolMessage message, ProtocolSession fromSession) throws ProtocolException{
		// this method should never be called - the protocol is not expected to
		// handle incoming packets!
		throw new ProtocolException("Method push in SSF.OS.OSPF.test.Configurator must not be called!");
    }
        
    /** Container class for a single update event */
    public class UpdateEvent {
    	/** DML config associated with this update */
  		public dmlConfig cfg;
  		/** time */
  		public double t;
  	}
}

